package org.example.utils

import com.alibaba.fastjson.{JSON, JSONArray, JSONObject}
import org.apache.commons.lang3.StringUtils
import org.example.common.Logging
import org.example.constant.ApolloConst
import scalaj.http.{Http, HttpResponse}

import java.awt.geom.Point2D
import scala.collection.mutable.{ArrayBuffer, ListBuffer}

/**
 * 经纬度相关工具类
 */
object GPSUtil extends Logging {
  val EARTH_RADIUS = 6371000

  /**
   * 根据经纬度调用世纪高通API获取 逆地理信息
   *
   * @param lon
   * @param lat
   * @return
   */
  def getAddressAndLocationCode(lon: Double, lat: Double, detail: Int, road: Int, rowNumber: Int): JSONObject = {
    try {
      val param = scala.collection.immutable.Map("location" -> s"$lon,$lat", "detail" -> s"$detail", "road" -> s"$road", "rowNumber" -> s"$rowNumber")
      val response: HttpResponse[String] = Http("http://222.85.218.31:60055/service/coder/reverse-geocoding").params(param).asString
      try {
        JSON.parseObject(response.body)
      } catch {
        case _ =>
          println(s"JSON parse ERROR: ${response.body}")
          null
      }
    } catch {
      case _ =>
        null
    }
  }

  /**
   * 描述：根据经纬度获取详细地址，区县代码
   *
   * @param lon 经度
   * @param lat 维度
   * @return
   */
  def getLocationCode(lon: Double, lat: Double): (String, String, String) = {
    val locationCode: JSONObject = getAddressAndLocationCode(lon, lat, 1, 0, 0)
    if (locationCode != null && locationCode.getIntValue("errcode") == 0) {
      val data: JSONObject = locationCode.getJSONObject("data")
      val addr: String = data.getString("address")
      val areaCode = data.getJSONObject("dist").getString("code")
      val areaName = data.getJSONObject("dist").getString("value")
      (addr, areaCode, areaName)
    } else {
      (s"逆地理接口调用失败[$lon,$lat]", "100000", "未知区域")
    }

  }

  /**
   * 描述：根据地址获取经纬度
   *
   * @param cityName 城市名
   * @param address  地址
   * @return
   */
  def getGpsByAddress(cityName: String, address: String): (String, String) = {
    val locationCode: JSONObject = getGps(cityName, address)
    if (locationCode != null && locationCode.getIntValue("status") != 0) {
      var lon = "-1"
      var lat = "-1"
      val pois: JSONArray = locationCode.getJSONArray("pois")
      if (!pois.isEmpty) {
        val data: JSONObject = pois.getJSONObject(0)
        val location = data.getString("location")
        val locationString = location.split(",")
        if (locationString.length > 1) {
          lon = locationString(0)
          lat = locationString(1)
        }
        (lon, lat)
      } else {
        (lon, lat)
      }

    } else {
      ("-1", "-1")
    }

  }

  /**
   * 描述：根据地址获取省份城市县级
   *
   * @param cityName 城市名
   * @param address  地址
   * @return
   */
  def getCityByAddress(cityName: String, address: String): (String, String, String) = {
    println(cityName + ":" + address)
    val locationCode: JSONObject = getGps(cityName, address)
    if (locationCode != null && locationCode.getIntValue("status") != 0) {
      var provinceName = "未知"
      var cityName = "未知"
      var adname = "未知"
      val pois: JSONArray = locationCode.getJSONArray("pois")
      if (!pois.isEmpty) {
        val data: JSONObject = pois.getJSONObject(0)
        provinceName = data.getString("pname")
        cityName = data.getString("cityname")
        adname = data.getString("adname")
        (provinceName, cityName, adname)
      } else {
        (provinceName, cityName, adname)
      }

    } else {
      ("未知", "未知", "未知")
    }

  }


  /**
   * 根据经纬度调用世纪高通API获取 逆地理信息
   *
   * @param lon
   * @param lat
   * @return
   */
  def getAddressAndLocationCode1(lon: Double, lat: Double, detail: Int, road: Int, rowNumber: Int): JSONObject = {
    try {
      val param = scala.collection.immutable.Map("location" -> s"$lon,$lat", "detail" -> s"$detail", "road" -> s"$road", "rowNumber" -> s"$rowNumber")
      val response: HttpResponse[String] = Http("http://10.11.57.105:60050/service/coder/reverse-geocoding").params(param).asString
      try {
        JSON.parseObject(response.body)
      } catch {
        case _ =>
          println(s"JSON parse ERROR: ${response.body}")
          null
      }
    } catch {
      case _ =>
        null
    }
  }


  /**
   * 根据地址获取经纬度（高德）
   *
   * @param cityName
   * @param address
   * @return
   */
  def getGps(cityName: String, address: String): JSONObject = {
    var newAddress = address
    if (address != null && !address.contains(cityName)) {
      newAddress = cityName + address
    }
    try {
      val param = scala.collection.immutable.Map("keywords" -> s"${newAddress}", "key" -> s"${ApolloConst.amapByAddressKey}")
      val response: HttpResponse[String] = Http("http://restapi.amap.com/v3/place/text").params(param).asString
      try {
        JSON.parseObject(response.body)
      } catch {
        case _ =>
          println(s"JSON parse ERROR: ${response.body}")
          null
      }
    } catch {
      case _ =>
        null
    }
  }

  /**
   * 获取两点间的路径和时长
   *
   * @param lon1
   * @param lat1
   * @param lon2
   * @param lat2
   */
  def getRouteWayAndTime(lon1: Double, lat1: Double, lon2: Double, lat2: Double): (Int, Int) = {
    //距离
    var distance = -1
    //时间
    var duration = -1
    val locationCode: JSONObject = getRouteJson(lon1, lat1, lon2, lat2)
    if (locationCode != null && locationCode.getIntValue("status") != 0) {
      val route: JSONObject = locationCode.getJSONObject("route")
      val paths: JSONArray = route.getJSONArray("paths")
      if (!paths.isEmpty) {
        val path: JSONObject = paths.getJSONObject(0)
        distance = path.getInteger("distance")
        duration = path.getInteger("duration")
      }
    }
    (distance, duration)

  }

  /**
   * 获取两点间的路径和时长
   *
   * @param lon1
   * @param lat1
   * @param lon2
   * @param lat2
   */
  def getRouteJson(lon1: Double, lat1: Double, lon2: Double, lat2: Double): JSONObject = {
    try {
      val param = scala.collection.immutable.Map("origin" -> s"${lon1.toString + "," + lat1.toString}", "destination" -> s"${lon2.toString + "," + lat2.toString}", "key" -> s"${ApolloConst.amapKey}")
      val response: HttpResponse[String] = Http("https://restapi.amap.com/v3/direction/driving").params(param).asString
      try {
        JSON.parseObject(response.body)
      } catch {
        case _ =>
          println(s"JSON parse ERROR: ${response.body}")
          null
      }
    } catch {
      case _ =>
        null
    }
  }


  //根据经纬度获取详细地址，区县代码
  def getLocationCode1(lon: Double, lat: Double): (String, String, String) = {
    val locationCode: JSONObject = getAddressAndLocationCode1(lon, lat, 1, 0, 0)
    if (locationCode != null && locationCode.getIntValue("errcode") == 0) {
      val data: JSONObject = locationCode.getJSONObject("data")
      val addr: String = data.getString("address")
      val areaCode = data.getJSONObject("dist").getString("code")
      val areaName = data.getJSONObject("dist").getString("value")
      (addr, areaCode, areaName)
    } else {
      (s"逆地理接口调用失败[$lon,$lat]", "100000", "未知区域")
    }

  }

  //根据经纬度获取详细地址，区县代码
  def getLocationCode2(lon: Double, lat: Double): (String, String, String, String) = {
    val locationCode: JSONObject = getAddressAndLocationCode1(lon, lat, 1, 0, 0)
    if (locationCode != null && locationCode.getIntValue("errcode") == 0) {
      val data: JSONObject = locationCode.getJSONObject("data")
      val addr: String = data.getString("address")
      val areaCode = data.getJSONObject("dist").getString("code")
      val areaName = data.getJSONObject("dist").getString("value")
      val cityName = data.getJSONObject("city").getString("value")

      (addr, areaCode, areaName, cityName)
    } else {
      (s"逆地理接口调用失败[$lon,$lat]", "100000", "未知区域", "未知城市")
    }

  }

  //两点距离计算球面弧度
  def rad(d: Double): Double = {
    d * Math.PI / 180.0
  }

  //经纬度算距离,弧段算法，结果单位 米
  def getDistance(lon1: Double, lon2: Double, lat1: Double, lat2: Double): Double = {
    val radLat1 = rad(lat1)
    val radLat2 = rad(lat2)
    val a = radLat1 - radLat2
    val b = rad(lon1) - rad(lon2)
    var s = 2 * Math.asin(Math.sqrt(
      Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))
    s = s * EARTH_RADIUS
    s
  }

  //某点到电子围栏的最小距离
  def GetNearestDistance(point: Point2D, poly: List[Point2D]): Double = {
    val sides: ArrayBuffer[(Point2D, Point2D)] = new ArrayBuffer[(Point2D, Point2D)]()
    var minDis: Double = Double.MaxValue
    for (i <- poly.indices) {
      if (i == poly.length - 1) {
        sides += ((poly(i), poly.head))
      } else {
        sides += ((poly(i), poly(i + 1)))
      }
    }
    sides.foreach { side =>
      val a: Double = getDistance(side._1.getX, side._2.getX, side._1.getY, side._2.getY)
      val b: Double = getDistance(side._2.getX, point.getX, side._2.getY, point.getY)
      val c: Double = getDistance(point.getX, side._1.getX, point.getY, side._1.getY)
      if (b * b >= c * c + a * a) {
        if (c < minDis) minDis = c
      } else if (c * c >= b * b + a * a) {
        if (b < minDis) minDis = b
      } else {
        //海伦公式
        val l: Double = (a + b + c) / 2
        val s: Double = Math.sqrt(l * (l - a) * (l - b) * (l - c))
        val mid = 2 * s / a
        if (mid < minDis) minDis = mid
      }
    }
    minDis
  }

  /**
   * 经纬度小数点后1位约误差10km, 小数点后2位约为1km, 小数点后3位约为100米, 小数点后4位约为10米, 小数点后5位约为1米。
   *
   * @author heguantao
   * @param point     车辆的动态GPS点
   * @param poly      规定路线的GPS集合
   * @param threshold (经度上的阈值,纬度上的阈值)
   * @return 在阈值范围内的GPS集合
   */
  def getInnerPoints(point: Point2D, poly: List[Point2D], threshold: (Double, Double)): List[Point2D] = {
    poly.filter { roadPoint =>
      if ((roadPoint.getX - point.getX).abs < threshold._1 && (roadPoint.getX - point.getX).abs < threshold._2) { // 经度和纬度都小于阈值
        true
      } else {
        false
      }
    }
  }

  /**
   * 因存在9位经度，8位经度；8位纬度，7位纬度，需判断一下
   *
   * @param gpsType
   * @param gps
   * @return
   */
  def gpsSwitch(gpsType: String, gps: String): Double = {
    if ("lon".equalsIgnoreCase(gpsType)) {
      if (gps.length == 9) {
        gps.toDouble / 1000000
      } else if (gps.length == 8) {
        gps.toDouble / 100000
      } else {
        0
      }
    } else if ("lat".equalsIgnoreCase(gpsType)) {
      if (gps.length == 8) {
        gps.toDouble / 1000000
      } else if (gps.length == 7) {
        gps.toDouble / 100000
      } else {
        0
      }
    } else {
      0
    }
  }

  /**
   * 根据单个经纬度获取定位点信息
   *
   * @param lon
   * @param lat
   * @return
   */
  def getAddressAndLocationCode(lon: Double, lat: Double): (String, String) = {
    try {
      val addressAndLocationCode = getAddressAndLocationCodes(List((lon, lat)))
      addressAndLocationCode(0)
    } catch {
      case e: Exception => {
        error("异常信息==>经度：" + lon + "  纬度：" + lat)
        ("异常经纬度(" + lon + "|" + lat + ")无法获取地址信息", "330101")
      }
    }
  }

  /**
   * 批量获取经纬度信息
   *
   * @param locations
   * @return
   */
  def getAddressAndLocationCodes(locations: List[(Double, Double)]): IndexedSeq[(String, String)] = {
    var result: ListBuffer[(String, String)] = new ListBuffer[(String, String)]()
    if (!locations.isEmpty) {
      val stringBuilder = new StringBuilder()
      locations.foreach { location =>
        if (location._1 != 0.0 && location._2 != 0.0) {
          if (stringBuilder.length > 0) {
            stringBuilder.append("|")
          }
          stringBuilder.append(location._1 + "," + location._2)
        }
      }
      if (stringBuilder.length > 0) {
        val uri = String.format("https://restapi.amap.com/v3/geocode/regeo?output=json&location=%s&key=%s&batch=true",
          stringBuilder.toString(), ApolloConst.amapKey)
        val response = Http(uri)
          .header("content-type", "application/json")
          .method("GET")
          .timeout(connTimeoutMs = 5000, readTimeoutMs = 5000)
          .asString
        if (response.code == 200) {
          if (StringUtils.isNotEmpty(response.body)) {
            val body = JSON.parseObject(response.body)
            val status = body.getString("status")
            val infoCode = body.getString("infocode")
            val infos = body.getString("info")
            //返回值为 0 或 1，0 表示请求失败；1 表示请求成功
            if (!"1".equals(status)) {
              warn(String.format("amap api geocode response status is false,status:%s,info:%s,infocode:%s", status, infos, infoCode))
            } else {
              val regeoCodes = body.getJSONArray("regeocodes")
              (0 until regeoCodes.size()).foreach { index =>
                val regeoCode = regeoCodes.getJSONObject(index)
                val formattedAddress = regeoCode.getString("formatted_address")
                val addressComponent = regeoCode.getJSONObject("addressComponent")
                val adcode = addressComponent.getString("adcode")
                result += ((formattedAddress, adcode))
              }
            }
          }
        }
      }
    }

    result.toIndexedSeq
  }

  /**
   * 根据单个经纬度获取定位点详细信息,地址和道路信息
   *
   * @param lon
   * @param lat
   * @return
   */
  def getAddressAndRoad(lon: Double, lat: Double): (String, String, String, String) = {
    try {
      val result = getAddressAndRoads(List((lon, lat)))
      result(0)
    } catch {
      case e: Exception => {
        error("异常信息==>经度：" + lon + "  纬度：" + lat)
        ("-", "-", "", "")
      }
    }
  }

  /**
   * 根据多个经纬度获取定位点详细信息,地址和道路信息
   *
   * @param locations
   * @return
   */
  def getAddressAndRoads(locations: List[(Double, Double)]): IndexedSeq[(String, String, String, String)] = {
    var result: ListBuffer[(String, String, String, String)] = new ListBuffer[(String, String, String, String)]()
    if (!locations.isEmpty) {
      val stringBuilder = new StringBuilder()
      locations.foreach { location =>
        if (location._1 != 0.0 && location._2 != 0.0) {
          if (stringBuilder.length > 0) {
            stringBuilder.append("|")
          }
          stringBuilder.append(location._1 + "," + location._2)
        }
      }
      if (stringBuilder.length > 0) {
        val uri = String.format("https://restapi.amap.com/v3/geocode/regeo?output=json&location=%s&key=%s&batch=true&roadlevel=0&radius=150&extensions=all",
          stringBuilder.toString(), ApolloConst.amapKey)
        val response = Http(uri)
          .header("content-type", "application/json")
          .method("GET")
          .timeout(connTimeoutMs = 5000, readTimeoutMs = 5000)
          .asString
        if (response.code == 200) {
          if (StringUtils.isNotEmpty(response.body)) {
            val body = JSON.parseObject(response.body)
            val status = body.getString("status")
            val infoCode = body.getString("infocode")
            val infos = body.getString("info")
            //返回值为 0 或 1，0 表示请求失败；1 表示请求成功
            if (!"1".equals(status)) {
              warn(String.format("amap api geocode response status is false,status:%s,info:%s,infocode:%s", status, infos, infoCode))
            } else {
              val regeoCodes = body.getJSONArray("regeocodes")
              (0 until regeoCodes.size()).foreach { index =>
                val regeoCode = regeoCodes.getJSONObject(index)
                val formattedAddress = regeoCode.getString("formatted_address")
                val addressComponent = regeoCode.getJSONObject("addressComponent")
                val roads = regeoCode.getJSONArray("roads")
                var roadId = ""
                var roadName = ""
                if (!roads.isEmpty) {
                  val road = roads.getJSONObject(0)
                  roadId = road.getString("id")
                  roadName = road.getString("name")
                }
                val adcode = addressComponent.getString("adcode")
                result += ((formattedAddress, adcode, roadId, roadName))
              }
            }
          }
        }
      }
    }

    result.toIndexedSeq
  }

  /**
   * 根据城市编码和道路名称获取道路相关交通态势
   *
   * @param adcode
   * @param name
   * @return
   */
  def getRoadInfo(adcode: String, name: String): IndexedSeq[(String, String, String)] = {
    val results = ListBuffer[(String, String, String)]()

    val uri = String.format("https://restapi.amap.com/v3/traffic/status/road?output=json&key=%s&adcode=%s&name=%s&extensions=all",
      ApolloConst.amapKey, adcode, name)
    val response = Http(uri)
      .header("content-type", "application/json")
      .method("GET")
      .timeout(connTimeoutMs = 5000, readTimeoutMs = 5000)
      .asString
    if (response.code == 200) {
      if (StringUtils.isNotEmpty(response.body)) {
        val body = JSON.parseObject(response.body)
        val status = body.getString("status")
        val infoCode = body.getString("infocode")
        val infos = body.getString("info")
        //返回值为 0 或 1，0 表示请求失败；1 表示请求成功
        if (!"1".equals(status)) {
          warn(String.format("amap api geocode response status is false,status:%s,info:%s,infocode:%s", status, infos, infoCode))
        } else {
          val roads = body.getJSONObject("trafficinfo").getJSONArray("roads")
          (0 until roads.size()).foreach { index =>
            val road = roads.getJSONObject(index)
            val direction = road.getString("direction")
            val angle = road.getString("angle")
            val polyline = road.getString("polyline")

            results += ((direction, angle, polyline))
          }
        }
      }
    }

    results.toIndexedSeq
  }

  def main(args: Array[String]): Unit = {
    println(getGpsByAddress("重庆市", "重庆市江津区白云道铝兴路12号付3号门面"))
    val t = getRoadInfo("331000", "东环大道")
    println(getAddressAndRoad(122.215800, 30.224099))
    print(t)
  }
}
